/*
 * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.analysis.api.symbols

import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.lifetime.KaLifetimeOwner
import org.jetbrains.kotlin.analysis.api.symbols.markers.KaNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KaSymbolPointer
import org.jetbrains.kotlin.name.Name

/**
 * [KaSymbol]s are a core concept in the Analysis API, representing declarations visible to Kotlin. Symbols represent not only declarations
 * in source files, but also declarations from libraries, and those generated by compiler plugins and the compiler itself.
 *
 * While the Kotlin PSI represents the syntax structure of Kotlin code, symbols provide access to semantic information about declarations.
 * "Semantic" refers to the meaning of code and relationships between declarations from the compiler's standpoint, as opposed to a purely
 * syntactic representation.
 *
 * @see KaSymbolProvider
 * @see org.jetbrains.kotlin.analysis.api.components.KaSymbolRelationProvider
 */
public interface KaSymbol : KaLifetimeOwner {
    /**
     * The kind of data source or generation the symbol originates from (e.g. source files, libraries, compiler plugins).
     *
     * The [origin] is not to be confused with the [location], which is concerned with the symbol's location in code.
     */
    public val origin: KaSymbolOrigin

    /**
     * The kind of location where a symbol is declared in code, e.g. [KaSymbolLocation.TOP_LEVEL] for top-level declarations.
     *
     * The [location] is not to be confused with the [origin], which is concerned with the kind of data source or generation the symbol
     * originates from.
     */
    public val location: KaSymbolLocation

    /**
     * A [PsiElement] corresponding to this [KaSymbol].
     *
     * The property is only defined for the following [origin]s:
     *
     * - [KaSymbolOrigin.SOURCE]
     * - [KaSymbolOrigin.JAVA_SOURCE]
     * - [KaSymbolOrigin.JAVA_LIBRARY]
     * - [KaSymbolOrigin.LIBRARY] (the [psi] source element is taken from the generated Kotlin class file)
     *
     * For other (usually synthetic) origins, the [psi] might be `null` or non-null. The Analysis API makes no consistent guarantees about
     * the PSI of such origins.
     */
    public val psi: PsiElement?

    /**
     * Creates a [KaSymbolPointer] which can be used to retrieve the symbol in a different [analyze][org.jetbrains.kotlin.analysis.api.analyze]
     * block (i.e. a different [KaSession][org.jetbrains.kotlin.analysis.api.KaSession]).
     */
    public fun createPointer(): KaSymbolPointer<KaSymbol>
}

/**
 * Returns the name of the [KaSymbol] if it has one.
 */
public val KaSymbol.name: Name?
    get() = if (this is KaNamedSymbol) name else null

/**
 * Returns the symbol's [PsiElement] if its type is [PSI], and otherwise throws a [ClassCastException].
 *
 * @see KaSymbol.psi
 */
public inline fun <reified PSI : PsiElement> KaSymbol.psi(): PSI =
    psi as PSI

/**
 * Returns the symbol's [PsiElement] if its type is [PSI], or `null` otherwise.
 *
 * @see KaSymbol.psi
 */
public inline fun <reified PSI : PsiElement> KaSymbol.psiSafe(): PSI? =
    psi as? PSI

/**
 * Returns the symbol's [PsiElement]. Returns `null` if its [KaSymbol.origin] is not [KaSymbolOrigin.SOURCE]. Throws a [ClassCastException]
 * if its type is not [PSI].
 *
 * @see KaSymbol.psi
 */
public inline fun <reified PSI : PsiElement> KaSymbol.sourcePsi(): PSI? {
    // TODO: support Java sources after KT-53669
    if (origin != KaSymbolOrigin.SOURCE) return null

    return psi as PSI
}

/**
 * Returns the symbol's [PsiElement] if its type is [PSI] and [KaSymbol.origin] is [KaSymbolOrigin.SOURCE] or [KaSymbolOrigin.JAVA_SOURCE],
 * and `null` otherwise.
 *
 * @see KaSymbol.psiSafe
 */
public inline fun <reified PSI : PsiElement> KaSymbol.sourcePsiSafe(): PSI? {
    if (origin != KaSymbolOrigin.SOURCE && origin != KaSymbolOrigin.JAVA_SOURCE) return null

    return psi as? PSI
}

/**
 * [KaSymbolOrigin] describes the kind of data source (sources, libraries, etc.) or generation (SAM constructors, compiler plugins, etc.)
 * the symbol originates from.
 *
 * In cases where the origin is a generated declaration (e.g. [SAM_CONSTRUCTOR], [DELEGATED], [JAVA_SYNTHETIC_PROPERTY]), not only the
 * generated declaration itself will have this origin, but all declarations contained in it as well. For example, while
 * [KaSamConstructorSymbol] has a [SAM_CONSTRUCTOR] origin, its value parameters also share the same [SAM_CONSTRUCTOR] origin.
 */
public enum class KaSymbolOrigin {
    /**
     * A declaration directly written in Kotlin source code.
     */
    SOURCE,

    /**
     * A declaration generated by the Kotlin compiler as a member of another declaration, such as:
     *
     *  - Default constructors of classes
     *  - The `copy`, `component{N}`, `toString`, `equals`, and `hashCode` functions of data classes
     *  - The `valueOf` and `values` functions of enum classes
     *  - The implicit `it` parameter of a lambda
     */
    SOURCE_MEMBER_GENERATED,

    /**
     * A declaration from a compiled Kotlin library (`.jar` or `.klib` files).
     */
    LIBRARY,

    /**
     * A declaration from Java source code.
     */
    JAVA_SOURCE,

    /**
     * A declaration from a compiled Java library.
     *
     * In contrast to [LIBRARY], a [JAVA_LIBRARY] declaration comes from compiled Java code. The Kotlin compiler can tell the difference by
     * the absence of Kotlin class metadata.
     */
    JAVA_LIBRARY,

    /**
     * A declaration from a synthetic constructor function used to build instances of [SAM interfaces](https://kotlinlang.org/docs/fun-interfaces.html#sam-conversions).
     *
     * @see KaSamConstructorSymbol
     */
    SAM_CONSTRUCTOR,

    /**
     * A declaration from a type alias constructor. Class type aliases define their own synthetic constructors which can be called in place
     * of the original class's constructor.
     *
     * #### Example
     *
     * ```kotlin
     * class MyClass(param: String)
     *
     * typealias MyTypeAlias = MyClass
     *
     * fun usage() {
     *   MyClass()     // <-- this reference points to a regular constructor symbol
     *   MyTypeAlias() // <-- this reference points to a typealiased constructor symbol
     * }
     * ```
     */
    TYPEALIASED_CONSTRUCTOR,

    /**
     * A declaration from a synthetic callable generated by the Kotlin compiler for callable intersections.
     *
     * #### Example
     *
     * ```
     * interface A { fun foo() }
     * interface B { fun foo() }
     *
     * interface C : A, B
     * ```
     *
     * A synthetic function `C.foo` will be created from the intersection of `A.foo` and `B.foo` with an [INTERSECTION_OVERRIDE] origin.
     *
     * @see org.jetbrains.kotlin.analysis.api.components.KaSymbolRelationProvider.intersectionOverriddenSymbols
     */
    INTERSECTION_OVERRIDE,

    /**
     * A declaration from a synthetic callable generated by the Kotlin compiler for callable type substitutions.
     *
     * #### Example
     *
     * ```
     * interface A<T> { fun foo(): T }
     * interface B : A<Int> { }
     * ```
     *
     * Interface `B` will have a member `B.foo(): Int` with the [SUBSTITUTION_OVERRIDE] origin. Note the substituted return type.
     */
    SUBSTITUTION_OVERRIDE,

    /**
     * A declaration from a member generated by the compiler for interface delegation. This occurs when a class delegates the implementation
     * of an interface to another object.
     *
     * #### Example
     *
     * ```
     * interface MyInterface {
     *     fun perform()
     * }
     *
     * class MyClass(private val delegate: MyInterface) : MyInterface by delegate
     * ```
     *
     * The compiler generates a member function `perform()` in `MyClass` that calls the `perform()` function from `delegate`.
     */
    DELEGATED,

    /**
     * A declaration from a synthetic property generated by the compiler for a Java field associated with a getter or setter. This allows
     * Java fields to be accessed like Kotlin properties.
     *
     * @see KaSyntheticJavaPropertySymbol
     */
    JAVA_SYNTHETIC_PROPERTY,

    /**
     * The backing field of a property declaration (a field that stores the value of the property).
     *
     * #### Example
     *
     * ```
     * class MyClass {
     *     var property: Int = 0
     *         get() = field
     *         set(value) { field = value }
     * }
     * ```
     *
     * The `field` identifier in the getter and setter refers to the backing field of `property`.
     *
     * @see KaBackingFieldSymbol
     */
    PROPERTY_BACKING_FIELD,

    /**
     * A declaration generated by a compiler plugin.
     */
    PLUGIN,

    /**
     * A declaration from a dynamic Kotlin/JS scope.
     *
     * See the Kotlin documentation about [dynamic types](https://kotlinlang.org/docs/dynamic-type.html).
     */
    JS_DYNAMIC,

    /**
     * A Kotlin/Native forward declaration.
     *
     * See the [forward declarations](https://kotlinlang.org/docs/multiplatform-compatibility-guide.html#new-approach-to-forward-declarations)
     * section in the Kotlin documentation.
     */
    NATIVE_FORWARD_DECLARATION,
}
